/*
	Script: Engine.js
		Foo - Framework for Rapid RIA Development

	License:
		MIT-style license.

	Foo Copyright:
		Copyright (c) 2007 Rizqi Ahmad, <http://riiv.net>

	Foo Credits:
		- Uses prototype.js <http://prototype.conio.net/> (c) 2005 Sam Stephenson sam [at] conio [dot] net, MIT-style license
		- Documentation by Rizqi Ahmad (raid_ox [at] yahoo [dot] de).
*/


/* 
	Class: foo
		Namespaces of all Foo functions/properties.

	Properties:
		version - current version of Foo
		ns - used XML Namespace
		nsURL - foo XML Namespace URI
		escapeChars - Array containing characters to be escaped
		entityChars - Array containing entities to be replaced
		closure - String containing function wrapper to create template functions
		fuidPrefix - prefix for FUID (Foo Unique IDentifier)
		sectionClassName - Section declarer classname
		reversion - if true, the DOM attributes need to be reversed
		idx - counter for generating unique variables
		isLoaded - returns true if Foo is loaded
		noload - set to true if you don't want Foo to be loaded automaticly
*/
var foo = {
	version: '0.2.1',
	
	ns: 'foo',
	nsURI: 'http://foo.riiv.net/namespace',
	escapeChars: [['\\', '\\\\'], ['\'', '\\\''], ['"', '\\"'], ['\r', '\\r'], ['\n','\\n'], ['\t','\\t']],
	entityChars: [['&amp;', '&'], ['&gt;', '>'], ['&lt;', '<'], ['&and;', '\^'], ['&frasl;', '\/']],
	closure: [
		[	'var $text = [];',
			'$text.print = function(text) {',
					'this.push((typeof text == "number")?text:(text||""));',
			'};'
		].join('\n'),
		
		'return $text.join("");'
	],
	
	fuidPrefix: 'fuid',
	sectionClassName: 'foo-section',
	reversion: false,
	idx: 0,
	isLoaded: false,
	noLoad: false,
	rootTemplate: null,
	sectionTemplates: []
};

/* 
	Section: Core Functions
		Core Function provided in Foo
	
	Usage:
		> foo.functionName()
*/

/*
	Function: initialize
		Initialize Foo Framework.
*/
foo.initialize = function(){
	if(!foo.noLoad && !foo.isLoaded){
		foo.isLoaded = true;
		foo.reversion = foo.Utils.reverseTest();
		foo.getNamespace();
		foo.fireEvent('initialize');
		document.body.id = 'root';
		if(foo.rootTemplate){
			new foo.Section(document.body, foo.rootTemplate);
		}else{
			foo.idx = 0;
			new foo.Section(document.body, null);
		}
		
	}
};

/*
	Function: extend
		Copies all properties from the source to the target object.
	
	Parameters:
		target - Target Object
		source - Source Object
	
	Returns:
		returns the target object.
*/
foo.extend = function(target, source){
	source = source || target || {};
	target = source == target ? this : target;
	
	for(var i in source)
		target[i] = source[i];
	
	return target;
};

foo.extend({
/*
	Function: namespace
		Create namespace.
	
	Parameters:
		ns1 - String Namespace 1
		ns2 - String Namespace 2
		... - String Namespace 3 etc.
*/
	namespace: function(){
		var arg, o;
		for(var i=0; i<arguments.length; i++){
			args = arguments[i].split('.');
			window[args[0]] = window[args[0]] || {};
			o = window[args[0]];
			for(var j=1; j<args.length; j++){
				o[args[j]] = o[args[j]] || {};
				o = o[args[j]];
			}
		}
	},
/*
	Function: getNamespace
		Get Foo XML Namespace.
*/
	getNamespace: function(){
		var html = document.body;
		$A(html.attributes).each(function(a){
			if(a.nodeName && a.nodeValue && (a.nodeValue == foo.nsURI || a.nodeValue == foo.nsURI+'/')){
				foo.ns = a.nodeName.split(':').pop();
				throw $break;
			}
		});
	}
});

/*
	Class: foo.Browser
		Browser detection.
	
	Properties:
		IE - returns true in Internet Explorer
		Opera - returns true in Opera
		Gecko - returns true in Gecko based browsers
		Webkit - returns true in Webkit based browsers
*/
foo.Browser = {
		IE: /*@cc_on!@*/false,
		Opera: !!window.opera,
		Gecko: (navigator.product == 'Gecko') && !window.opera && !/*@cc_on!@*/false,
		Webkit: (navigator.userAgent.indexOf('AppleWebKit/')> -1)
};

/*
	Class: Element.methods
		Extends Element using Prototype DOM Extension.
	
	Usage:
		> element.functionName()
*/
Element.addMethods({
/*
	Function: getSection
		Get associated Foo Section of current element.
	
	Returns:
		Section
*/
	getSection: function(element){
		if(element._parentSection)
			return element._parentSection;
		var parentSection, el = element;
		while(el.parentNode){
			if(el.section){
				parentSection = el.section;
				break;
			}
			el = el.parentNode;
		}
		element._parentSection = parentSection;
		return parentSection || false;
	},
/*
	Function: getData
		Get data of current element form elementData storage service.
	
	Parameters:
		name - Data name
	
	Returns:
		Data
*/
	getData: function(element, name){
		var section = $(element).getSection();
		if(section)
			return section.getElementData(element.id, name);
	}
});

/*
	Section: Foo Global Events
		Foo Global Event Service
	
	Usage:
		> foo.functionName()
		> foo.propertyName
*/
foo.extend({
/*
	Property: events
		Contains event containers.
*/
	events: {
		initialize: [],
		sectionInit: [],
		sectionUpdate: [],
		sectionRefresh: []
	},

/*
	Function: addListener
		Attach listener to an event.
	
	Parameters:
		name - Event name
		listener - Listener function
*/
	addListener: function(name, listener){
		foo.events[name].push(listener);
	},

/*
	Function: removeListener
		Detach listener to an event.
	
	Parameters:
		name - Event name
		listener - Listener function
*/
	removeListener: function(name, fn){
		foo.events[name] = foo.events[name].without(fn);
	},

/*
	Function: fireEvent
		Fire event and execute attached listeners.
	
	Parameters:
		name - Event name
		[args] - Arguments
*/
	fireEvent: function(){
		var args = $A(arguments), name = args.shift();
		foo.events[name] = foo.events[name].reject(function(fn){return fn.apply(foo, args);});
	}
});

/*
	Class: foo.Console
		Foo Console System
	
	Properties:
		protocol - Array protocol
*/
foo.Console = {
	protocol: [],

/*
	Function: log
		Add message to Console protocol.
	
	Parameters:
		type - log type (Error/Message)
		message - Message text
*/
	log: function(type, message){
		this.protocol.push({type: type, message: message, date: new Date});
	},

/*
	Function: clear
		Clear protocol
*/
	clear: function(){
		this.protocol = [];
	}
};

/*
	Section: Text Parser Functions
	
	Properties:
		printTag - Print Tag, default set to ${}
	
	Usage:
		> foo.functionName()
		> foo.propertyName
*/
foo.extend({
	printTag: ['${', '}'],

/*
	Function: parseText
		Parse template text to javascript function.
	
	Parameters:
		text - String text
*/
	parseText: function(text){
		var opener, closer, result = [];
		text = unescape(text);
		while(text){
			opener = text.indexOf(foo.printTag[0]);
			closer = opener + text.substring(opener).indexOf(foo.printTag[1]) + foo.printTag[1].length;
			
			if(opener != -1) {
				if(text[opener-1] == '\\'){
					result.push(foo.textPrint(text.substring(0,opener-foo.printTag[0].length)));
					result.push(foo.textPrint(text.substring(opener,closer)));
				}else{
					if(text.substring(0,opener))
						result.push(foo.textPrint(text.substring(0,opener)));
					if(text.substring(opener,closer))
						result.push(foo.scriptPrint(text.substring(opener+foo.printTag[0].length,closer-foo.printTag[1].length)));
				}
				text = text.substring(closer);
			} else {
				if(text)
					result.push(foo.textPrint(text));
				text = '';
			}
		}
		
		return result.join('\n');
	},
/*
	Function: parseInline
		Parse text and convert it to Javascript string.
	
	Parameters:
		text - String text
*/
	parseInline: function(text){
		var opener, closer, result = [];
		text = unescape(text);
		while(text){
			opener = text.indexOf(foo.printTag[0]);
			closer = text.indexOf(foo.printTag[1]) + foo.printTag[1].length;
			
			if(opener != -1) {
				if(text[opener-1] == '\\'){
					result.push(text.substring(0,opener-foo.printTag[0].length).toJSON()+text.substring(opener,closer).toJSON());
				}else{
					if(text.substring(0,opener))
						result.push(text.substring(0,opener).toJSON());
					if(text.substring(opener,closer))
						result.push('('+text.substring(opener+foo.printTag[0].length,closer-foo.printTag[1].length)+')');
					text = text.substring(closer);
				}
			} else {
				if(text)
					result.push(text.toJSON());
				text = '';
			}
		}
		return result.join('+');
	},
/*
	Function: textPrint
		Print text output in template function.
	
	Parameters:
		text - Text to be printed
*/
	textPrint: function(text){
		return "$text.print("+text.toJSON()+");";
	},
/*
	Function: scriptPrint
		Print the output of a script in template function.
*/
	scriptPrint: function(script){
		return "$text.print("+script+");";
	}
});

/*
	Section: Module Service
		Foo Module Service
	
	Properties:
		modules - module storage
		baseModule - base modules
*/
foo.extend({
	modules: {},
	baseModules: [],
/*
	Function: addModule
		Add Module Plugin
	
	Parameters:
		name - module name
		mod - Module Object
*/
	addModule: function(name, mod){
		mod.name = name;
		if(mod.standard)
			foo.baseModules.push(mod);
		foo.modules[name] = mod;
	}
});

foo.compile = function(){
	var text = [];
	text.push('foo.idx ='+foo.idx+';');
	text.push('foo.sectionTemplates = '+foo.Utils.serialize(foo.sectionTemplates)+';');
	text.push('foo.rootTemplate = '+foo.Utils.serialize({content:root.templateSource, template: root.template, events: root.events})+';');
	return text.join('\n');
}

// onDOMReady
Object.extend(Event, {
  _domReady : function() {
    if (arguments.callee.done) return;
    arguments.callee.done = true;

    if (this._timer)  clearInterval(this._timer);
    
    this._readyCallbacks.each(function(f) { f() });
    this._readyCallbacks = null;
},
  onDOMReady : function(f) {
    if (!this._readyCallbacks) {
      var domReady = this._domReady.bind(this);
      
      if (document.addEventListener)
        document.addEventListener("DOMContentLoaded", domReady, false);
        
        /*@cc_on @*/
        /*@if (@_win32)
            document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
            document.getElementById("__ie_onload").onreadystatechange = function() {
                if (this.readyState == "complete") domReady(); 
            };
        /*@end @*/
        
        if (/WebKit/i.test(navigator.userAgent)) { 
          this._timer = setInterval(function() {
            if (/loaded|complete/.test(document.readyState)) domReady(); 
          }, 10);
        }
        
        Event.observe(window, 'load', domReady);
        Event._readyCallbacks =  [];
    }
    Event._readyCallbacks.push(f);
  }
});

// Initialize
Event.onDOMReady(foo.initialize);